Aller au contenu principal

Tests unitaires

Les tests unitaires consistent à vérifier le bon fonctionnement de petites portions de code, appelées "unités". En Java, ces unités sont généralement des méthodes, des classes, ou des ensembles de classes.

Pourquoi utiliser les tests unitaires en Java ?

Les avantages sont les mêmes que pour tout autre langage :

  • Détection précoce des erreurs : Identifier les bugs dès le début du développement.
  • Assurance de la qualité du code : Garantir la robustesse et la fiabilité du code.
  • Amélioration du code plus sûr : Modifier le code sans introduire de nouvelles erreurs.

Comment utiliser les tests unitaires en Java ?

  1. Identifier les unités à tester : Déterminez les méthodes, classes ou ensembles de classes à tester.

  2. Écrire les tests : Utilisez un framework de tests unitaires, comme JUnit, pour écrire les tests. Un test unitaire typique en Java suit ces étapes :

    • Préparation des données d'entrée : Créer les objets, variables, etc., nécessaires pour exécuter la méthode à tester.
    • Appel de la méthode à tester : Exécuter la méthode avec les données d'entrée.
    • Vérification des résultats : Utiliser des assertions (par exemple, assertEquals de JUnit) pour comparer les résultats obtenus avec les résultats attendus.
  3. Exécuter les tests : Utilisez JUnit (ou un autre outil) pour exécuter les tests et obtenir un rapport sur les succès et les échecs.

Création du fichier de test en Java

En Java, les fichiers de test sont généralement créés dans un répertoire src/test/java (parallèle au répertoire src/main/java contenant le code source). Les classes de test ont souvent le même nom que les classes testées, suivi de "Test" (par exemple, MaClasse.java et MaClasseTest.java).

Voici un exemple concret avec JUnit :

// fichier: MaClasse.java
public class MaClasse {
public int addition(int a, int b) {
return a + b;
}
}

// fichier: MaClasseTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class MaClasseTest {
@Test
void testAddition() {
MaClasse maClasse = new MaClasse();
assertEquals(5, maClasse.addition(2, 3));
assertEquals(0, maClasse.addition(-1, 1));
assertEquals(0, maClasse.addition(0, 0));
}
}

Dans cet exemple :

  • MaClasse.java contient la classe à tester.
  • MaClasseTest.java contient la classe de test.
  • L'annotation @Test indique que la méthode testAddition est une méthode de test.
  • assertEquals vérifie que les résultats de la méthode addition sont égaux aux valeurs attendues.

Situations logiques à tester (avec exemples complets)

1) Cas nominal (entrée valide)

// fichier: Calculatrice.java
public class Calculatrice {
public int addition(int a, int b) {
return a + b;
}
}

// fichier: CalculatriceTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatriceTest {
@Test
void addition_casNominal() {
Calculatrice calc = new Calculatrice();
assertEquals(7, calc.addition(3, 4));
}
}

2) Cas limites (bornes min / max)

// fichier: Normalisation.java
public class Normalisation {
public int clampInt(int value) {
if (value < 0) return 0;
if (value > 100) return 100;
return value;
}
}

// fichier: NormalisationTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class NormalisationTest {
@Test
void clamp_limites_entiers() {
Normalisation n = new Normalisation();
assertEquals(0, n.clampInt(Integer.MIN_VALUE));
assertEquals(100, n.clampInt(Integer.MAX_VALUE));
assertEquals(0, n.clampInt(-5));
assertEquals(100, n.clampInt(150));
assertEquals(42, n.clampInt(42));
}
}

3) Cas nuls / vides

// fichier: Texte.java
public class Texte {
public int longueur(String s) {
if (s == null) return 0;
return s.length();
}
}

// fichier: TexteTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class TexteTest {
@Test
void longueur_null_ou_vide() {
Texte texte = new Texte();
assertEquals(0, texte.longueur(null));
assertEquals(0, texte.longueur(""));
assertEquals(3, texte.longueur("abc"));
}
}

4) Cas invalides (exceptions attendues)

// fichier: Validateur.java
public class Validateur {
public int racineCarree(int n) {
if (n < 0) throw new IllegalArgumentException("n doit être >= 0");
return (int) Math.sqrt(n);
}
}

// fichier: ValidateurTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class ValidateurTest {
@Test
void racineCarree_exception() {
Validateur v = new Validateur();
assertThrows(IllegalArgumentException.class, () -> v.racineCarree(-1));
}
}

5) Cas d'erreur (division par zéro)

// fichier: Division.java
public class Division {
public int diviser(int a, int b) {
if (b == 0) throw new ArithmeticException("Division par zéro");
return a / b;
}
}

// fichier: DivisionTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class DivisionTest {
@Test
void division_par_zero() {
Division d = new Division();
assertThrows(ArithmeticException.class, () -> d.diviser(10, 0));
}
}

6) Branches conditionnelles (if / else)

// fichier: Notes.java
public class Notes {
public String mention(int note) {
if (note >= 90) return "A";
if (note >= 80) return "B";
if (note >= 70) return "C";
return "D";
}
}

// fichier: NotesTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class NotesTest {
@Test
void mention_toutes_branches() {
Notes notes = new Notes();
assertEquals("A", notes.mention(95));
assertEquals("B", notes.mention(85));
assertEquals("C", notes.mention(75));
assertEquals("D", notes.mention(60));
}
}

7) Boucles (0, 1, plusieurs itérations)

// fichier: Sommes.java
import java.util.List;

public class Sommes {
public int somme(List<Integer> valeurs) {
int total = 0;
for (int v : valeurs) {
total += v;
}
return total;
}
}

// fichier: SommesTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;

public class SommesTest {
@Test
void somme_boucles() {
Sommes s = new Sommes();
assertEquals(0, s.somme(List.of()));
assertEquals(5, s.somme(List.of(5)));
assertEquals(6, s.somme(List.of(1, 2, 3)));
}
}

8) États internes (avant / après)

// fichier: Compteur.java
public class Compteur {
private int valeur = 0;

public void incrementer() {
valeur++;
}

public int getValeur() {
return valeur;
}
}

// fichier: CompteurTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CompteurTest {
@Test
void incrementer_modifie_etat() {
Compteur c = new Compteur();
assertEquals(0, c.getValeur());
c.incrementer();
assertEquals(1, c.getValeur());
}
}

9) Idempotence (appel répété = même résultat)

// fichier: Nettoyeur.java
public class Nettoyeur {
public String nettoyer(String s) {
if (s == null) return "";
return s.trim().toLowerCase();
}
}

// fichier: NettoyeurTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class NettoyeurTest {
@Test
void nettoyer_idempotent() {
Nettoyeur n = new Nettoyeur();
String uneFois = n.nettoyer(" HeLLo ");
String deuxFois = n.nettoyer(uneFois);
assertEquals(uneFois, deuxFois);
}
}

Bonnes pratiques pour les tests unitaires en Java

  • Écrire les tests avant le code : Cela vous aide à mieux définir les exigences et à concevoir un code plus testable.
  • Tests indépendants : Chaque test doit être indépendant des autres.
  • Couverture de code : Visez une couverture de code maximale.
  • Tests lisibles et maintenables : Utilisez des noms clairs pour les méthodes de test et les variables.

Exécuter les tests

  • Dans un IDE (IntelliJ / VS Code / Eclipse) : clic droit sur la classe de test → Run.
  • Avec Maven : mvn test
  • Avec Gradle : gradle test

Frameworks de tests unitaires en Java

JUnit est le framework de tests unitaires le plus populaire en Java. Il fournit des outils pour écrire, exécuter et organiser les tests. TestNG est une autre option populaire, offrant des fonctionnalités similaires et quelques extensions.